/*
 * Created on Jan 1, 2005
 */
package org.scohen.juploadr.uploadapi.zooomr.upload;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.ConnectException;
import java.net.NoRouteToHostException;
import java.net.UnknownHostException;
import java.util.LinkedList;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;

import javax.xml.parsers.DocumentBuilderFactory;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity;
import org.apache.commons.httpclient.methods.multipart.Part;
import org.apache.commons.httpclient.methods.multipart.StringPart;
import org.scohen.juploadr.Messages;
import org.scohen.juploadr.app.UploadImage;
import org.scohen.juploadr.app.tags.TagParser;
import org.scohen.juploadr.event.ImageUploadResponse;
import org.scohen.juploadr.event.MonitorFilePart;
import org.scohen.juploadr.event.UploadCompleteEvent;
import org.scohen.juploadr.event.UploadEvent;
import org.scohen.juploadr.event.UploadStatusMonitor;
import org.scohen.juploadr.upload.HttpClientFactory;
import org.scohen.juploadr.uploadapi.CommunicationException;
import org.scohen.juploadr.uploadapi.ProtocolException;
import org.scohen.juploadr.uploadapi.UploadFailedException;
import org.scohen.juploadr.uploadapi.zooomr.DefaultZooomrHandler;
import org.scohen.juploadr.uploadapi.zooomr.ZooomrMethod;
import org.scohen.juploadr.uploadapi.zooomr.ZooomrRestApi;
import org.scohen.juploadr.uploadapi.zooomr.authentication.ZooomrAccount;

/**
 * @author steve
 */
public class ZooomrUpload extends ZooomrMethod {
    public static final int STATUS_OK = 1;
    public static final int STATUS_FAILED = 0;

    private static final String URL = "http://upload.zooomr.com/services/upload/"; //$NON-NLS-1$
    private UploadImage image;
    private UploadStatusMonitor monitor;
    private SortedMap<String, String> params = new TreeMap<String, String>();
    private ZooomrUploadResponseHandler handler;

    public ZooomrUpload(UploadImage img, UploadStatusMonitor monitor) {
        this.image = img;
        this.monitor = monitor;

        handler = new ZooomrUploadResponseHandler(this);
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        factory.setValidating(false);
        factory.setNamespaceAware(false);

    }

    public boolean execute() throws ProtocolException, CommunicationException {
        HttpClient client = HttpClientFactory.getHttpClient();

        // checkAuthorization(client);
        this.monitor.uploadStarted(new UploadEvent(image, 0, true, false));

        PostMethod post = new PostMethod(URL);
        List<Part> parts = getParts();
        MultipartRequestEntity entity = new MultipartRequestEntity(parts.toArray(new Part[parts.size()]), post
                .getParams());
        post.setRequestEntity(entity);

        try {

            int status = client.executeMethod(post);
            if (status == HttpStatus.SC_OK) {
                // deal with the response
                try {
                    String response = post.getResponseBodyAsString();
                    post.releaseConnection();
                    boolean success = parseResponse(response);
                    if (success) {
                        image.setState(UploadImage.STATE_UPLOADED);
                        ImageUploadResponse resp = new ImageUploadResponse(handler.getPhotoID());
                        this.monitor.uploadFinished(new UploadCompleteEvent(resp, image));
                    } else {
                        // TODO: Error status reported by Flickr should be
                        // properly reported
                        throw new UploadFailedException(Messages.getString("juploadr.ui.error.status")); //$NON-NLS-1$
                    }

                } catch (IOException e) {
                    // TODO: Is it safe to assume the upload failed here?
                    this.fail(Messages.getString("juploadr.ui.error.response.unreadable") //$NON-NLS-1$
                            + e.getMessage(), e);
                }
            } else {
                this.fail(Messages.getString("juploadr.ui.error.bad.http.response", status), null); //$NON-NLS-1
            }
        } catch (ConnectException ce) {
            this.fail(Messages.getString("juploadr.ui.error.unable.to.connect"), ce); //$NON-NLS-1$
        } catch (NoRouteToHostException route) {
            this.fail(Messages.getString("juploadr.ui.error.no.internet"), route); //$NON-NLS-1$
        } catch (UnknownHostException uhe) {
            this.fail(Messages.getString("juploadr.ui.error.unknown.host"), uhe); //$NON-NLS-1$

        } catch (HttpException e) {
            this.fail(Messages.getString("juploadr.ui.error.http.exception") + e, e); //$NON-NLS-1$
        } catch (IOException e) {
            this.fail(Messages.getString("juploadr.ui.error.simple.ioexception") + e.getMessage() + "" //$NON-NLS-1$ //$NON-NLS-2$
                    + e, e);
        }
        return true;
    }

    /**
     * Notifies the monitor of failure and throws UploadFailedException.
     * 
     * @param message
     * @param cause
     * @throws UploadFailedException
     */
    private void fail(String message, Throwable cause) throws CommunicationException {
        this.monitor.uploadFailed(new UploadEvent(image, 0, true, false, message));
        if (cause != null) {
            throw new CommunicationException(message, cause);
        } else {
            throw new CommunicationException(message);
        }
    }

    private List<Part> getParts() throws UploadFailedException {
        LinkedList<Part> parts = new LinkedList<Part>();
        try {
            addNonNullStringPart(parts, "auth_token", ((ZooomrAccount) ZooomrRestApi.getInstance()
                    .getCurrentAccount()).getToken());
            addNonNullStringPart(parts, "api_key", ZooomrRestApi.ZOOOMR_API_KEY);
            addNonNullStringPart(parts, "title", image.getTitle()); //$NON-NLS-1$
            addNonNullStringPart(parts, "tags", TagParser.toRequestString(image.getTags())); //$NON-NLS-1$
            addNonNullStringPart(parts, "description", image.getDescription()); //$NON-NLS-1$
            addBooleanPart(parts, "is_public", image.isPubliclyVisible()); //$NON-NLS-1$
            addBooleanPart(parts, "is_friend", image.isFriendViewable()); //$NON-NLS-1$
            addBooleanPart(parts, "is_family", image.isFamilyViewable()); //$NON-NLS-1$
            MonitorFilePart file = new MonitorFilePart("photo", new File(image //$NON-NLS-1$
                    .getImagePath()), image);
            file.addStateChangeListener(monitor);
            parts.add(file);
            parts.add(new StringPart("api_sig", getSignature()));

        } catch (FileNotFoundException e) {
            throw new UploadFailedException(Messages.getString(
                    "juploadr.ui.error.file.notfound", image.getImagePath())); //$NON-NLS-1$
        }

        return parts;
    }

    private void addBooleanPart(List<Part> parts, String paramName, boolean shouldSet) {
        String value = (shouldSet) ? "1" : "0";
        StringPart part = new StringPart(paramName, value);
        parts.add(part);
        params.put(paramName, value);
    }

    /**
     * @param parts
     * @param string
     * @param title
     */
    private void addNonNullStringPart(List<Part> parts, String paramName, String paramValue) {
        if (paramValue != null) {
            StringPart part = new StringPart(paramName, paramValue);
            part.setCharSet("UTF-8");
            parts.add(part);
            params.put(paramName, paramValue);
        }
    }

    public DefaultZooomrHandler getResponseHandler() {
        return handler;
    }

    public SortedMap<String, String> getParams() {
        return params;
    }

    public boolean isAuthorized() {
        return false;
    }

}
